package com.apes.framework.plugin.fsm.schedule;

import com.apes.framework.jpa.DefaultPersistListener;
import com.apes.framework.jpa.Model;
import com.apes.framework.jpa.comment.Comment;
import com.apes.framework.jpa.generator.ApesGenerator;
import com.apes.framework.util.DateUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.SelectBeforeUpdate;

import javax.persistence.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.apes.framework.util.DateUtil.*;

/**
 * 功能：排班
 *
 * @author xul
 * @create 2021-01-07 11:37
 */
@Getter
@Setter
@SelectBeforeUpdate
@DynamicInsert
@DynamicUpdate
@Entity
@EntityListeners(DefaultPersistListener.class)
@Table(name = WorkSchedule.CONST_TABLE_NAME)
public class WorkSchedule extends Model {
    public static final String CONST_TABLE_NAME = "fsm_work_schedule";

    @Id
    @Column(name = "id", length = 32)
    @ApesGenerator(code = CONST_TABLE_NAME, name = "排班")
    @Comment("编码")
    private String id;

    @Column(name = "name", nullable = false, length = 120)
    @Comment("名称")
    private String name;

    @OneToMany(mappedBy = "workSchedule", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<WorkScheduleItem> workScheduleItems = new ArrayList<>();

    public List<WorkScheduleItem> getWorkScheduleItems() {
        if (workScheduleItems == null) return new ArrayList<>();
        for (WorkScheduleItem item : workScheduleItems) {
            item.setWorkSchedule(this);
        }
        return workScheduleItems;
    }

    /**
     * 计算到期时间
     *
     * @param baseDate 基值
     * @param total    时长（单位：分钟）
     * @return 到期时间
     */
    public Date calcExpired(Date baseDate, int total) {
        if (this.workScheduleItems == null || this.workScheduleItems.isEmpty()) {
            return DateUtil.add(baseDate, Calendar.MINUTE, total);
        }

        /**按开始时间排序**/
        getWorkScheduleItems().sort(Comparator.comparingLong(WorkScheduleItem::caleStartTime));

        /**计算每天时长（单位：分钟）**/
        int daily = getWorkScheduleItems().stream().mapToInt(item -> calcDuration(item.getStart(), item.getEnd())).sum();

        /**计算所需天数**/
        int days = total / daily;
        Date result = DateUtil.add(baseDate, Calendar.DATE, days);  /**没有排除节假日**/

        /**计算剩余（小于一天）**/
        int residue = total % daily; /*剩余量*/
        return cale(result, residue, wrap(result));
    }

    private Date cale(Date baseDate, int total, Collection<WorkScheduleItem> items) {
        Date result = baseDate;
        boolean succeed = false;
        int handle = 0;  /*已处理量*/
        for (WorkScheduleItem item : items) {
            int duration = caleDuration(result, item);
            int increment; /*增量*/
            if (handle + duration >= total) {
                increment = total - handle;
                succeed = true;
            } else {
                increment = duration;
            }
            handle += increment;
            result = succeed(result, item, increment);
            if (succeed) break;
        }

        int surplus = total - handle; /*剩余量*/

        /*量不足（跨天）*/
        if ((!succeed) || (surplus != 0)) {
            WorkScheduleItem firstItem = workScheduleItems.stream().findFirst().orElse(null);
            if (firstItem == null) {
                throw new RuntimeException("没有定义排班计划！");
            }
            result = DateUtil.add(result, Calendar.DATE, 1);
            result = formatDate(result, firstItem.getStart());
            result = cale(result, surplus, wrap(result));
        }
        return result;
    }

    /**
     * 基于某个时间计算有效时长（单位：分钟）
     **/
    private int caleDuration(Date baseDate, WorkScheduleItem item) {
        String base = format(baseDate, SHOW_TIME_FORMAT);
        return time(item.getStart()) <= time(base) ? calcDuration(base, item.getEnd()) : calcDuration(item.getStart(), item.getEnd());
    }

    /**
     * 计算两个时间点之间的时长（单位：分钟）
     **/
    @SneakyThrows
    private int calcDuration(String start, String end) {
        SimpleDateFormat sdf = new SimpleDateFormat(SHOW_TIME_FORMAT);
        return Long.valueOf((sdf.parse(end).getTime() - sdf.parse(start).getTime()) / (1000 * 60)).intValue();
    }

    @SneakyThrows
    private long time(String source) {
        SimpleDateFormat sdf = new SimpleDateFormat(SHOW_TIME_FORMAT);
        return sdf.parse(source).getTime();
    }

    @SneakyThrows
    private Date succeed(Date baseDate, WorkScheduleItem item, int increment) {
        String base = format(baseDate, SHOW_TIME_FORMAT);
        if (time(item.getStart()) > time(base)) {
            base = item.getStart();
        }
        return DateUtil.add(formatDate(baseDate, base), Calendar.MINUTE, increment);
    }

    @SneakyThrows
    private Date formatDate(Date baseDate, String base) {
        SimpleDateFormat sdf = new SimpleDateFormat(SHOW_DATE_FORMAT + " " + SHOW_TIME_FORMAT);
        return sdf.parse(format(baseDate, SHOW_DATE_FORMAT) + " " + base);
    }

    private List<WorkScheduleItem> wrap(Date baseDate) {
        long baseTime = time(format(baseDate, SHOW_TIME_FORMAT));
        return getWorkScheduleItems().stream()
                .filter(item -> (time(item.getStart()) <= baseTime && time(item.getEnd()) >= baseTime) || time(item.getStart()) > baseTime)
                .collect(Collectors.toList());
    }

    @Transient
    private String description;

    public String getDescription() {
        StringBuffer sb = new StringBuffer();
        getWorkScheduleItems().sort(Comparator.comparingLong(WorkScheduleItem::caleStartTime));
        for (WorkScheduleItem item : getWorkScheduleItems()) {
            if (sb.length() != 0) {
                sb.append(",");
            }
            sb.append(item.getStart()).append("-").append(item.getEnd());
        }
        return sb.length() == 0 ? name : name + "（" + sb.toString() + "）";
    }
}
